﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.ComponentModel;
using System.Threading;

using BEX.Core;
using BEX.Core.Exchange;
using BEX.DataAccess;

namespace BEX.RuleMonitor
{
    public class RuleMonitor
    {

        #region Properties

        #region Threading Properties

        private object _sync = new object();
        private Thread[] _workers;
        private int _consumerCount = 2;
        private bool _stop = false;
        private System.Timers.Timer _producerStart;

        #endregion


        private SafeQueue _rules = new SafeQueue();
        private NotificationManager _nManager = new NotificationManager();

        //private BackgroundWorker[] _ruleTesters;
        //private SemaphoreSlim _consumerLock;

        private int _success = 0;
        private int _failed = 0;
        private int _error = 0;
        private int _alreadyTested = 0;
        private int _totalTests = 0;

        //        private object _sharedLock = new object();
        //      private object _flatRuleLock = new object();

        Dictionary<CurrencyType, int> _baseCurrencyIndex = new Dictionary<CurrencyType, int>();
        Dictionary<CurrencyType, int> _counterCurrencyIndex = new Dictionary<CurrencyType, int>();

        private Tick[, ,] _current;
        #endregion

        #region Monitor Management
        public RuleMonitor()
        {
            //_consumerLock = new SemaphoreSlim(_consumerCount, _consumerCount);
        }

        /// <summary>
        /// Establish Dictionaries that translate a currency to an array index
        /// </summary>
        private void InitCurrencyIndexes()
        {
            List<CurrencyType> _base = ExchangeDAL.GetAllBaseCurrencies();

            _baseCurrencyIndex = new Dictionary<CurrencyType, int>();

            for (int i = 0; i < _base.Count; ++i)
            {
                _baseCurrencyIndex.Add(_base[i], i);
            }

            List<CurrencyType> _counter = ExchangeDAL.GetAllCounterCurrencies();

            _counterCurrencyIndex = new Dictionary<CurrencyType, int>();

            for (int i = 0; i < _counter.Count; ++i)
            {
                _counterCurrencyIndex.Add(_counter[i], i);
            }
        }

        public void Start()
        {
            Exchange[] _exchanges;

            InitCurrencyIndexes();

            _exchanges = new Exchange[Enum.GetValues(typeof(ExchangeType)).Cast<ExchangeType>().Count()];

            _current = new Tick[_exchanges.Length, _baseCurrencyIndex.Count, _counterCurrencyIndex.Count];

            _producerStart = new System.Timers.Timer(10000);
            _producerStart.AutoReset = true;
            _producerStart.Elapsed += _producerStart_Elapsed;
            _producerStart.Start();


            _workers = new Thread[_consumerCount];

            for (var i = 0; i < _workers.Length; ++i)
            {
                _workers[i] = new Thread(TestRules);
                _workers[i].Start();
            }
        }

        public void Stop()
        {
            _stop = true;

            _producerStart.Stop();
            _producerStart.Dispose();

            lock (_sync)
            {
                Monitor.PulseAll(_sync);
            }

            foreach (Thread _t in _workers)
                _t.Join();
        }


        #endregion

        #region Consumer

        private void TestRules()
        {

            lock (_sync)
            {
                Monitor.Wait(_sync);

            }

            AlertRule _toTest = null;

            while (!_stop)
            {
                _toTest = _rules.GetNext();

                if (_toTest != null)
                {
                    ++_totalTests;
                    try
                    {
                        if (null != _current[(int)_toTest.SourceExchange, _baseCurrencyIndex[_toTest.Pair.Base], _counterCurrencyIndex[_toTest.Pair.Counter]])
                        {
                            var _spot = (Tick)_current[(int)_toTest.SourceExchange, _baseCurrencyIndex[_toTest.Pair.Base], _counterCurrencyIndex[_toTest.Pair.Counter]];

                            if (_toTest.Test(_spot))
                            {
                                NotifyRule(_toTest);
                                ++_success;
                            }
                            else
                                ++_failed;
                        }
                        else
                            Error("Consumer: Cannot test rule, current Tick is null (" + _toTest.SourceExchange.ToString() + " - " + _toTest.Pair.Base.ToString() + "/" + _toTest.Pair.Counter.ToString() + ")");
                    }
                    catch (Exception ex)
                    {
                        ++_error;
                        Error(ex.Message);
                    }
                }
                else
                {
                    if (!_stop)
                    {
                        lock (_sync)
                        {
                            InvokeStatusEvent("Rule Queue is empty.  Waiting...", ExchangeType.UNKNOWN);
                            Monitor.Wait(_sync);
                            InvokeStatusEvent("Pulse received", ExchangeType.UNKNOWN);
                        }
                    }
                }
            }
        }


        #endregion

        #region Producer
        protected void _producerStart_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            _producerStart.Stop();

            try
            {
                Produce();
            }
            catch (Exception ex)
            {
                Error(ex.Message);
            }
            finally
            {
                _producerStart.Start();
            }
        }

        protected void Produce()
        {
            if (_rules.QueueCount > 0)
            {
                Error("!!!!!!!!QUEUE COUNT IS NON-ZERO!!!!!!!!");
            }

            InvokeStatusEvent(LastTestRoundStatus(), ExchangeType.UNKNOWN);

            InvokeStatusEvent("-------------------------------------------", ExchangeType.UNKNOWN);

            var _updated = FillNextConsecutiveTicks();

            _success = 0;
            _failed = 0;
            _error = 0;
            _alreadyTested = 0;
            _totalTests = 0;

            if (_updated > 0)
            {
                FlatAmountRule.GetFlatRuleList().ForEach(r => _rules.AddToQueue(r));
                PercentageRule.GetPercentRuleList().ForEach(r => _rules.AddToQueue(r));
                IncrementRule.GetIncrementRuleList().ForEach(r => _rules.AddToQueue(r));
                ArbitrageRule.GetArbitrageRuleList().ForEach(r => _rules.AddToQueue(r));

                InvokeStatusEvent("Producer: Queued " + _rules.QueueCount.ToString() + " Rules.", ExchangeType.UNKNOWN);

                // Start Workers
                lock (_sync)
                {
                    InvokeStatusEvent("Pulsing Workers.", ExchangeType.UNKNOWN);
                    Monitor.PulseAll(_sync);
                }
            }
            else
                InvokeStatusEvent("Producer: No Ticks Updated, skipping tests.", ExchangeType.UNKNOWN);
        }
        #endregion

        #region Data Support

        /// <summary>
        /// Verify me
        /// </summary>
        private int FillNextConsecutiveTicks()
        {
            var _count = 0;

            CurrencyType[] _base;
            CurrencyType[] _counter;
            //List<CurrencyPair> _pairs;

            foreach (ExchangeType _exch in Enum.GetValues(typeof(ExchangeType)).Cast<ExchangeType>())
            {
                if (_exch == ExchangeType.UNKNOWN)
                    continue;

                _base = ExchangeDAL.GetSupportedBaseCurrencies(_exch);

                foreach (CurrencyType _b in _base)
                {
                    if (_b == CurrencyType.UNKNOWN)
                        continue;

                    _counter = ExchangeDAL.GetSupportedCounterCurrencies(_exch, _b);

                    foreach (CurrencyType _c in _counter)
                    {
                        if (_c == CurrencyType.UNKNOWN)
                            continue;

                        Tick _toAdd = null;
                        Tick _previous = null;

                        if (_current[(int)_exch, _baseCurrencyIndex[_b], _counterCurrencyIndex[_c]] == null)
                        {
                            CurrencyPair _newP = new CurrencyPair();
                            _newP.Base = _b;
                            _newP.Counter = _c;

                            _toAdd = TickDAL.GetCurrentTick(_exch, _newP);
                        }
                        else
                        {
                            _previous = (Tick)_current[(int)_exch, _baseCurrencyIndex[_b], _counterCurrencyIndex[_c]];

                            _toAdd = TickDAL.GetNextConsecutiveTick(_exch, _previous);

                            if (_previous == _toAdd)
                            {
                                Error("Producer: Previous and Next are the same");
                                continue;
                            }

                            if (null != _previous && null != _toAdd)
                            {
                                if (_previous.Id == _toAdd.Id)
                                {
                                    Error("Producer: Previous & ToAdd had the same ID");
                                    continue;
                                }
                            }
                        }

                        if (null != _toAdd)
                        {
                            ++_count;
                            _current[(int)_exch, _baseCurrencyIndex[_b], _counterCurrencyIndex[_c]] = _toAdd;
                        }
                    }
                }
            }

            InvokeStatusEvent("Producer: " + _count.ToString() + " Ticks Added/Updated.", ExchangeType.UNKNOWN);

            return _count;
        }

        #endregion

        #region Status Updates / Notifications

        private string LastTestRoundStatus()
        {
            return ("Tested " + _totalTests.ToString() + ":  (Success - " + _success.ToString() + ") (Failed - " + _failed.ToString() + ") (Error - " + _error.ToString() + ") (Previously Tested - " + _alreadyTested.ToString() + ")");
        }

        private void NotifyRule(AlertRule toNotify)
        {
            var _movement = string.Empty;

            switch (toNotify.GetType().ToString())
            {
                case ("BEX.RuleMonitor.PercentageRule"):
                    _movement = "(PercentRule) " + ((PercentageRule)toNotify).Percent + "% in " + (((PercentageRule)toNotify).TimeSpan / 60).ToString() + " minutes";
                    break;
                case ("BEX.RuleMonitor.FlatAmountRule"):
                    _movement = "(FlatRule) " + ((FlatAmountRule)toNotify).Amount + " BTC";
                    break;
                case ("BEX.RuleMonitor.IncrementRule"):
                    _movement = "(IncrementRule) " + ((IncrementRule)toNotify).Increment + " in " + (((IncrementRule)toNotify).TimeSpan / 60).ToString() + " minutes";
                    break;
                case ("BEX.RuleMonitor.ArbitrageRule"):
                    _movement = "(ArbitrageRule) " + ((ArbitrageRule)toNotify).Percent;
                    break;
                default:
                    _movement = "Unknown";
                    break;
            }

            InvokeStatusEvent("Notify rule - ID: " + toNotify.ID.ToString() + " X: " + toNotify.SourceExchange.ToString() + " R: " + _movement, toNotify.SourceExchange);

            _nManager.GenerateNotifications(toNotify);
        }

        #endregion

        #region Error Handling

        protected void InvokeStatusEvent(string status, ExchangeType exch)
        {
            if (OnUpdateStatus == null)
                return;

            var args = new RuleMonitorProgressEventArgs(status, exch);

            OnUpdateStatus.Raise(null, args);
        }

        public delegate void StatusUpdateHandler(object sender, RuleMonitorProgressEventArgs e);

        public event StatusUpdateHandler OnUpdateStatus;

        private void Error(string message)
        {
            InvokeStatusEvent("Error: " + message, ExchangeType.UNKNOWN);
        }

        #endregion
    }
}





